Skip to main content

Sumo Squats AI Feature

Squat with your feet wider apart.

Sumo Squats

Feature

feature = .fitness(.sumoSquats)
feature = .fitness(.sumoSquats, style: customOrConditionalStyle)

Basic Implementation

To show results you'll need to modify your view ZStack, which is we assume is setup as described in the Getting Started Guide:

ZStack(alignment: .top) {
QuickPoseCameraView(useFrontCamera: true, delegate: quickPose)
QuickPoseOverlayView(overlayImage: $overlayImage)
}

The basic implementation will require displaying some text to the screen, start with declaring this value in your swiftui view.

@State private var feedbackText: String? = nil

And show this feedback text as an overlay to the view in your branding.

ZStack(alignment: .top) {
QuickPoseCameraView(useFrontCamera: true, delegate: quickPose)
QuickPoseOverlayView(overlayImage: $overlayImage)
}
.overlay(alignment: .center) {
if let feedbackText = feedbackText {
Text(feedbackText)
.font(.system(size: 26, weight: .semibold)).foregroundColor(.white).multilineTextAlignment(.center)
.padding(16)
.background(RoundedRectangle(cornerRadius: 8).foregroundColor(Color("AccentColor").opacity(0.8)))
.padding(.bottom, 40)
}
}

Note the above use of alignment in .overlay(alignment: .center), you can modify this to move the overlay around easily to say the bottom: .overlay(alignment: .bottom).

For this basic version it fills the feedback text with the Sumo Squat result as a percentage, and hides the text when the feature result is not available.

quickPose.start(features: [.fitness(.sumoSquats)], onFrame: { status, image, features, feedback, landmarks in
switch status {
case .success:
overlayImage = image
if let result = features.values.first {
feedbackText = "Sumo Squats: \(Int(result.value * 100))%"
} else {
feedbackText = nil
}
case .noPersonFound:
feedbackText = "Stand in view";
case .sdkValidationError:
feedbackText = "Be back soon";
}
})

Form Feedback

We recommend using the feature feedback to guide the user if an error occurs

quickPose.start(features: [.fitness(.sumoSquats)], onFrame: { status, image, features, feedback, landmarks in
switch status {
case .success:
overlayImage = image
if let result = features.values.first {
feedbackText = "Sumo Squats: \(Int(result.value * 100))%"
} else if let feedback = feedback.values.first, feedback.isRequired {
feedbackText = feedback.displayString
} else {
feedbackText = nil
}
case .noPersonFound:
feedbackText = "Stand in view";
case .sdkValidationError:
feedbackText = "Be back soon";
}
})

Body position: "Stand facing camera"

.body(feedback: standFacing, isRequired: true) 

Joint Visibility:

"Move left arm into view" "Move right arm into view" "Move upper body into view"
"Move left leg into view" "Move right leg into view" "Move lower body into view"
"Move whole body into view"

.group(action: .move, group:.arm(side: .left), direction:.intoView, isRequired: true) 
.group(action: .move, group:.arm(side: .right), direction:.intoView, isRequired: true)
.group(action: .move, group:.upperBody, direction:.intoView, isRequired: true)
.group(action: .move, group:.leg(side: .left), direction:.intoView, isRequired: true)
.group(action: .move, group:.leg(side: .right), direction:.intoView, isRequired: true)
.group(action: .move, group:.lowerBody, direction:.intoView, isRequired: true)
.group(action: .move, group:.wholeBody, direction:.intoView, isRequired: true)

Exercise Specific: "Move legs apart" "Lean upper body back"

.group(action: .move, group: .legs, direction: .apart, isRequired: true)
.group(action: .lean, group: .upperBody, direction: .back, isRequired: false)

Conditional Styling

To give user feedback consider using conditional styling so that when the user's measurement goes above a threshold, here 0.8, a green highlight is shown.

let greenHighlightStyle = QuickPose.Style(conditionalColors: [QuickPose.Style.ConditionalColor(min: 0.8, max: nil, color: UIColor.green)])
quickPose.start(features: [.fitness(.sumoSquats, style: customOrConditionalStyle)],
onFrame: { status, image, features, feedback, landmarks in ...
})

Sumo Squat Counting

To count the Sumo Squats declare a configurable threshold counter, which can be used to turn lots of our features into counts.

@State private var counter = QuickPoseThresholdCounter()

Then pass QuickPose's Sumo Squat result to the counter, and display in the feedback text declared above.

quickPose.start(features: [.fitness(.sumoSquats)], onFrame: { status, image, features, feedback, landmarks in
switch status {
case .success:
overlayImage = image
if let result = features.values.first {
let counterState = counter.count(result.value)
feedbackText = "\(counterState.count) Sumo Squats"
} else {
feedbackText = nil
}

case .noPersonFound:
feedbackText = "Stand in view";
case .sdkValidationError:
feedbackText = "Be back soon";
}
})

Sumo Squat Timing

To time the Sumo Squats declare a configurable threshold timer, which can be used to turn lots of our features into timers. For Sumo Squats, we suggest modifying the default threshold, taking account of expected camera positioning and tilt.

@State private var timer = QuickPoseThresholdTimer(threshold: 0.2)

Then pass the result's raw value to the timer, and display in the feedback text declared above.

quickPose.start(features: [.fitness(.sumoSquats)], onFrame: { status, image, features, feedback, landmarks in
switch status {
case .success:
overlayImage = image
if let result = features.values.first {
let timerState = timer.time(result.value)
feedbackText = String(format: "%.1f", timerState.time) + "secs"
} else {
feedbackText = nil
}

case .noPersonFound:
feedbackText = "Stand in view";
case .sdkValidationError:
feedbackText = "Be back soon";
}
})